33 QSPI 外部flash读写实验

开发板官网(https://www.edevkit.com/)
本系列教程目录:
- 瑞萨单片机开发教程[一] e2-studio + DAP LINK开发环境搭建
- 瑞萨单片机开发教程[二] Keil/RASC开发环境搭建
- 瑞萨单片机开发教程[三] 新建RASC工程
- 瑞萨单片机开发教程[四] GPIO输出——led实验
- 瑞萨单片机开发教程[五] GPIO输入——按键key实验
- 瑞萨单片机开发教程[六] ICU外部中断实验
- 瑞萨单片机开发教程[七] SCI UART串口通信实验
- 瑞萨单片机开发教程[八] DMAC 串口数据传输实验
- 瑞萨单片机开发教程[九] ADC 电压采集实验
QSPI 外部 Flash 读写实验
本章目标:
- 了解 RA6M5 处理器的 QSPI 模块
- 学会使用 RASC 配置 QSPI 模块,使用 API 读写数据。
硬件设计(略,参见配套硬件原理图)
软件设计
新建工程
复制一份 "07 SCI_UART_Recv_send" 修改文件夹名 "33 QSPI_flash",双击打开 EBF_RA6M5.uvprojx 工程,通过 Keil 的新建文件新建 qspi.c 和 qspi.h 保存到目录下的 \\src 文件夹下。
FSP 配置
Keil 界面选择 “Tools”→“RA Smart Configurator” 打开 RASC 界面。
在 “Pins” 添加 QSPI 设备。

添加 QSPI Stack 模块。

配置 QSPI Stack 参数。

配置完成之后可以按下快捷键"Ctrl + S"保存,最后点击 "Generate Project Content" 按钮,让软件自动生成配置代码即可。
程序设计
实现 QSPI 的读写函数,根据数据长度自动换页,写入一组字符串,一组 float 数据,一组 int 数据,分别读取出来,打印到串口查看数据是否正确。
- 打开 QSPI 设备
fsp_err_t R_QSPI_Open(spi_flash_ctrl_t * p_ctrl,
spi_flash_cfg_t const * const p_cfg);
- 擦除扇区
fsp_err_t R_QSPI_Erase(spi_flash_ctrl_t * p_ctrl,
uint8_t * const p_device_address,
uint32_t byte_count);
- 直接写指令数据
fsp_err_t R_QSPI_DirectWrite(spi_flash_ctrl_t * p_ctrl,
uint8_t const * const p_src,
uint32_t const bytes,
bool const read_after_write);
- 直接读指令数据
fsp_err_t R_QSPI_DirectRead(spi_flash_ctrl_t * p_ctrl,
uint8_t * const p_dest,
uint32_t const bytes);
- 写入页数据
fsp_err_t R_QSPI_Write(spi_flash_ctrl_t * p_ctrl,
uint8_t const * const p_src,
uint8_t * const p_device_address,
uint32_t byte_count);
- 读取页数据
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t * p_ctrl,
uint8_t * const p_device_address,
uint8_t * const p_dest,
uint32_t byte_count);
- 状态读取
fsp_err_t R_QSPI_StatusGet(spi_flash_ctrl_t * p_ctrl,
spi_flash_status_t * const p_status);
程序编写
qspi.h 头文件定义相关函数和宏定义。
#ifndef __BSP_QSPI_FLASH_H
#define __BSP_QSPI_FLASH_H
#include "hal_data.h"
#define W25X16 0xEF3015
#define W25Q16 0xEF4015
#define W25Q128 0XEF4018
#define W25Q64 0XEF4017
#define W25Q32 0xEF4016
#define AT25SF321B 0x1F8701
#define sFLASH_ID W25Q32
#define FLASH_ID_W25Q32JV 0xEF4016 // W25Q32JV
#define FLASH_ID_AT25SF321B 0x1F8701 // AT25SF321B
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define RESET_VALUE (0x00)
/*命令定义-开头*******************************/
#define WriteEnable 0x06
#define WriteDisable 0x04
#define ReadStatusReg 0x05
#define WriteStatusReg 0x01
#define ReadData 0x03
#define FastReadData 0x0B
#define FastReadDual 0x3B
#define PageProgram 0x02
#define BlockErase 0xD8
#define SectorErase 0x20
#define ChipErase 0xC7
#define PowerDown 0xB9
#define ReleasePowerDown 0xAB
#define DeviceID 0xAB
#define ManufactDeviceID 0x90
#define JedecDeviceID 0x9F
/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag 0x01
#define Dummy_Byte 0xFF
/*命令定义-结尾*******************************/
void QSPI_Flash_Init(void);
uint32_t QSPI_Flash_ReadID(void);
uint32_t QSPI_Flash_ReadDeviceID(void);
void QSPI_Flash_PowerDown(void);
void QSPI_Flash_WakeUp(void);
void QSPI_Flash_WriteEnable(void);
fsp_err_t QSPI_Flash_WaitForWriteEnd(void);
void QSPI_Flash_SectorErase(uint32_t adress);
void QSPI_Flash_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); //页写入
void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void QSPI_Flash_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
//static void qspi_d0_byte_write_standard (uint8_t byte);
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t *p_ctrl, uint8_t *p_src, uint8_t *const p_dest, uint32_t byte_count);
#endif
qspi.c 中实现 ID 读取,设备 ID 读取,用于判断 Flash 是否存在。
#include "qspi.h"
#include "stdio.h"
void QSPI_Flash_Init(void)
{
R_QSPI_Open(&g_qspi_flash_ctrl, &g_qspi_flash_cfg);
R_BSP_SoftwareDelay(10u, BSP_DELAY_UNITS_MILLISECONDS);
}
/**
* @brief 读取FLASH ID
* @param 无
* @retval FLASH ID
*/
uint32_t QSPI_Flash_ReadID(void)
{
unsigned char data[6] = {};
uint32_t back;
data[0] = JedecDeviceID;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, true);
R_QSPI_DirectRead(&g_qspi_flash_ctrl, &data[0], 3);
back = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2]);
return back;
}
/**
* @brief 读取FLASH 设备ID
* @param 无
* @retval FLASH ID
*/
uint32_t QSPI_Flash_ReadDeviceID(void)
{
unsigned char data[6] = {};
uint32_t back;
data[0] = DeviceID;
data[1] = 0xff;
data[2] = 0xff;
data[3] = 0xff;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 4, true);
R_QSPI_DirectRead(&g_qspi_flash_ctrl, &data[0], 3);
back = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2]);
return back;
}
/**
* @brief 进入掉电模式
* @param 无
*/
void QSPI_Flash_PowerDown(void)
{
unsigned char data[6] = {};
data[0] = PowerDown;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false);
}
/**
* @brief 唤醒
* @param 无
*/
void QSPI_Flash_WakeUp(void)
{
unsigned char data[6] = {};
data[0] = ReleasePowerDown;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false);
}
/**
* @brief 向FLASH发送 写使能 命令
* @param none
* @retval none
*/
void QSPI_Flash_WriteEnable(void)
{
unsigned char data[6] = {};
data[0] = WriteEnable;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false);
}
等待写入数据完成。
/**
* @brief 等待WIP(BUSY)标志被置0,即等待FLASH内部数据写入完毕
* @param 无
*/
fsp_err_t QSPI_Flash_WaitForWriteEnd(void)
{
spi_flash_status_t status = {.write_in_progress = true};
int32_t time_out = (INT32_MAX);
fsp_err_t err = FSP_SUCCESS;
do
{
/* Get status from QSPI flash device */
err = R_QSPI_StatusGet(&g_qspi_flash_ctrl, &status);
if (FSP_SUCCESS != err)
{
printf("R_QSPI_StatusGet Failed\r\n");
return err;
}
/* Decrement time out to avoid infinite loop in case of consistent failure */
--time_out;
if (RESET_VALUE >= time_out)
{
printf("\r\n ** Timeout : No result from QSPI flash status register ** \r\n");
return FSP_ERR_TIMEOUT;
}
}
while (false != status.write_in_progress);
return err;
}
擦除扇区函数,根据写入数据长度自动换页函数,读取函数。
/**
* @brief 擦除FLASH扇区
* @param SectorAddr:要擦除的扇区地址
* @retval 无
*/
void QSPI_Flash_SectorErase(uint32_t adress)
{
unsigned char data[6] = {};
data[0] = 0x06; //write_enable_command
data[1] = 0x20; //erase_command
data[2] = (uint8_t)(adress >> 16);
data[3] = (uint8_t)(adress >> 8);
data[4] = (uint8_t)(adress);
R_QSPI->SFMCMD = 1U;
R_QSPI->SFMCOM = data[0];
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[1], 4, false);
QSPI_Flash_WaitForWriteEnd();
}
/**
* @brief 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度,必须小于等于页大小
* @retval 无
*/
void QSPI_Flash_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}
/**
* @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度
* @retval 无
*/
void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
//mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0
Addr = WriteAddr % SPI_FLASH_PageSize;
//差count个数据值,刚好可以对齐到页地址
count = SPI_FLASH_PageSize - Addr;
//计算出要写多少整数页
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
//mod运算求余,计算出剩余不满一页的字节数
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
//Addr=0,则WriteAddr 刚好按页对齐 aligned
if (Addr == 0)
{
//NumByteToWrite < SPI_FLASH_PageSize
if (NumOfPage == 0)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}
else //NumByteToWrite > SPI_FLASH_PageSize
{
//先把整数页都写了
while (NumOfPage--)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
//若有多余的不满一页的数据,把它写完
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
//若地址与 SPI_FLASH_PageSize 不对齐
else
{
//NumByteToWrite < SPI_FLASH_PageSize
if (NumOfPage == 0)
{
//当前页剩余的count个位置比NumOfSingle小,一页写不完
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
//先写满当前页
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += count;
pBuffer += count;
//再写剩余的数据
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, temp);
QSPI_Flash_WaitForWriteEnd();
}
else //当前页剩余的count个位置能写完NumOfSingle个数据
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}
}
else // NumByteToWrite > SPI_FLASH_PageSize
{
//地址不对齐多出的count分开处理,不加入这个运算
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
// 先写完count个数据,为的是让下一次要写的地址对齐
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();
// 接下来就重复地址对齐的情况
WriteAddr += count;
pBuffer += count;
//把整数页都写
while (NumOfPage--)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize);
QSPI_Flash_WaitForWriteEnd();
WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
//若有多余的不满一页的数据,把它写完
if (NumOfSingle != 0)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
}
}
/**
* @brief 读取FLASH数据,减少ctrl这个标志
* @param pBuffer,存储读出数据的指针
* @param ReadAddr,读取地址
* @param NumByteToRead,读取数据长度
* @retval 无
*/
void QSPI_Flash_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
R_QSPI_Read(&g_qspi_flash_ctrl, pBuffer, ReadAddr, NumByteToRead);
}
在 hal_entry.c 文件中添加 qspi.h 头文件,添加测试代码,写入一组字符串,一组 float 数据,一组 int 数据,分别读出来打印到串口查看读写数据。
#define Flash_Opt_Addr 0x00000000
uint8_t txString[] = "either write something worth reading or do something worth writing.";
uint8_t rxString[sizeof(txString)];
double txFloat[10] = {1.2, 2.54, 3.47, 4.12, 5.22, 6.31, 7.15, 8.65, 9.71, 10.94};
double rxFloat[10] = {0};
int txInt[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 100};
int rxInt[10] = {0};
void hal_entry(void)
{
/* TODO: add your own code here */
led_Init();
uart0_Init();
QSPI_Flash_Init();
uint32_t Id = QSPI_Flash_ReadID();
uint32_t devId = QSPI_Flash_ReadDeviceID();
if(Id != 0 && devId != 0)
{
printf("FlashID: 0x%X, DeviceID: 0x%X \r\n", Id, devId);
//擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除
//擦除4K,即一个扇区,擦除的最小单位是扇区
QSPI_Flash_SectorErase(Flash_Opt_Addr);
//写一页,一页的大小为256个字节
QSPI_Flash_BufferWrite(txString, Flash_Opt_Addr, sizeof(txString));
printf("write string: %s \r\n", txString);
//将写入的数据读出来
QSPI_Flash_BufferRead(rxString, Flash_Opt_Addr, sizeof(txString));
printf("read string: %s \r\n", rxString);
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒
//前面已擦除整个扇区和写入第0页,现继续写入第1页和第2页
//写入float数据到第一页
QSPI_Flash_BufferWrite((void *)txFloat, SPI_FLASH_PageSize * 1, sizeof(txFloat));
printf("write float: ");
for(int i=0; i<10; i++)
printf("%f, ", txFloat[i]);
printf("\r\n");
//读取float数据
QSPI_Flash_BufferRead((void *)rxFloat, SPI_FLASH_PageSize * 1, sizeof(txFloat));
printf("read float: ");
for(int i=0; i<10; i++)
printf("%f, ", rxFloat[i]);
printf("\r\n");
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒
//写入第二页
QSPI_Flash_BufferWrite((void *)txInt, SPI_FLASH_PageSize * 2, sizeof(txInt));
printf("write int: ");
for(int i=0; i<10; i++)
printf("%d, ", txInt[i]);
printf("\r\n");
//读取整数数据
QSPI_Flash_BufferRead((void *)rxInt, SPI_FLASH_PageSize * 2, sizeof(txInt));
printf("read int: ");
for(int i=0; i<10; i++)
printf("%d, ", rxInt[i]);
printf("\r\n");
}
while(1)
{
LED1_TOGGLE();
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒
}
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
下载验证
连接下载器,将程序编译并下载到开发板之后,按下复位按键来复位开发板,程序启动初始化完。
可以看到打印读写的数据。